/***
 *  @func 发布订阅模式
 *  @msg 采集错误信息进行分发
 *  
 *  */
import ErrorStackParser from 'error-stack-parser' //通过error-stack-parser解析error堆栈  
import { options } from './options'
import { EVENTTYPES, STATUS_CODE } from '../common/constant'
import { transportData } from './reportData'
import { hashMapExist, getErrorUid } from '../utils/browser'
import { behaviors } from './behaviorCollection'
import { resetHttpData } from '../utils/initHttpData'
/* 相关接口 */
interface ErrorTarget {
    target?: {
        localName?: string;
    };
    error?: any;
    message?: string;
}
export const handlerEvents = {
    // 
    /**
     * @func 错误收集 react  vue
     * @params err:错误信息
     * @params vm:错误节点
     * @params info:错误事件/类型  比如(v-on)
     * */
    collectError(err: ErrorTarget) {

        if (!err.target || !err.target.localName) {
            /* 
                // 1.vue和react捕获的报错使用ev解析
                // 2. 异步错误使用ev.error解析
                // [参考](https://blog.csdn.net/m0_63685551/article/details/127529017)
            */
            const errJson = ErrorStackParser.parse(!err.target ? err : err.error)[0]
            const { fileName, columnNumber, lineNumber } = errJson
            // 上报去重
            const errorData = {
                type: EVENTTYPES.ERROR,
                status: STATUS_CODE.ERROR,
                time: Date.now(),
                message: err.message,
                fileName,
                line: lineNumber,
                column: columnNumber,
            };
            behaviors.push({
                type: EVENTTYPES.ERROR,
                category: behaviors.getCategory(EVENTTYPES.ERROR),
                data: errorData,
                time: Date.now(),
                status: STATUS_CODE.ERROR,
            })
            // 给错误事件定义hash唯一标识
            const hash: string = getErrorUid(
                `${EVENTTYPES.ERROR}-${err.message}-${fileName}-${columnNumber}`
            );
            if ((options.repeatCodeError && !hashMapExist(hash)) || (!options.repeatCodeError)) {
                return transportData.send(errorData);
            }

        }
        // 资源加载报错
        else if (err.target && err.target.localName) {
            return transportData.send({
                ...resourceTransform(err.target),
                type: EVENTTYPES.RESOURCE,
                status: STATUS_CODE.ERROR,
            });
        }
    },
    collectHashchange(data) {
        const { oldURL, newURL } = data;
        const { relative: from } = parseUrlToObj(oldURL);
        const { relative: to } = parseUrlToObj(newURL);
        behaviors.push({
            type: EVENTTYPES.HASHCHANGE,
            category: behaviors.getCategory(EVENTTYPES.HASHCHANGE),
            data: {
                from,
                to,
            },
            time: Date.now(),
            status: STATUS_CODE.OK,
        });
    },
    collectHistory(data): void {
        const { from, to } = data;
        // 定义parsedFrom变量，值为relative
        const { relative: parsedFrom } = parseUrlToObj(from);
        const { relative: parsedTo } = parseUrlToObj(to);
        behaviors.push({
            type: EVENTTYPES.HISTORY,
            category: behaviors.getCategory(EVENTTYPES.HISTORY),
            data: {
                from: parsedFrom ? parsedFrom : '/',
                to: parsedTo ? parsedTo : '/',
            },
            time: Date.now(),
            status: STATUS_CODE.OK,
        });
    },
    // 处理xhr、fetch回调
    collectHttp(data, type) {
        const result = resetHttpData(data)
        // 添加用户行为，去掉自身上报的接口行为
        if (!data.url.includes(options.dsn)) {
            behaviors.push({
                type,
                category: behaviors.getCategory(type),
                data: result,
                status: result.status,
                time: data.time,
            });
        }

        if (result.status === 'error') {
            // 上报接口错误
            transportData.send({ ...result, type, status: STATUS_CODE.ERROR });
        }
    },
    collectUnhandleRejection(ev) {
        const errJson = ErrorStackParser.parse(ev.reason)[0]
        const { fileName, columnNumber, lineNumber } = errJson
        const message = (ev.reason.message || ev.reason.stack);
        const data = {
            type: EVENTTYPES.UNHANDLEDREJECTION,
            status: STATUS_CODE.ERROR,
            time: Date.now(),
            message,
            fileName,
            line: lineNumber,
            column: columnNumber,
        };

        behaviors.push({
            type: EVENTTYPES.UNHANDLEDREJECTION,
            category: behaviors.getCategory(EVENTTYPES.UNHANDLEDREJECTION),
            time: Date.now(),
            status: STATUS_CODE.ERROR,
            data,
        });
        const hash: string = getErrorUid(
            `${EVENTTYPES.UNHANDLEDREJECTION}-${message}-${fileName}-${columnNumber}`
        );
        // 开启repeatCodeError第一次报错才上报
        if (!options.repeatCodeError || (options.repeatCodeError && !hashMapExist(hash))) {
            transportData.send(data);
        }
    }
}

/**
 * 将地址字符串转换成对象，
 * 输入：'https://github.com/xy-sea/web-see?token=123&name=11'
 * 输出：{
 *  "host": "github.com",
 *  "path": "/xy-sea/web-see",
 *  "protocol": "https",
 *  "relative": "/xy-sea/web-see?token=123&name=11"
 * }
 */
function parseUrlToObj(url: string) {
    if (!url) {
        return {};
    }
    const match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/);
    if (!match) {
        return {};
    }
    const query = match[6] || '';
    const fragment = match[8] || '';
    return {
        host: match[4],
        path: match[5],
        protocol: match[2],
        relative: match[5] + query + fragment,
    };
}

// 获取资源信息
function resourceTransform(target) {
    return {
        time: Date.now(),
        message:
            (sunNewStr(target.src as string, 120) || sunNewStr(target.href as string, 120)) +
            '; 资源加载失败',
        name: target.localName as string,
    };
}
function sunNewStr(str, len) {
    if (typeof str == 'string') {
        return (
            str.slice(0, len) +
            (str.length > len ? `:截取前${len}个字符` : '')
        );
    }
    return '';
}